feat(webauthn): related-origins validation (WebAuthn L3 §5.11)#219
feat(webauthn): related-origins validation (WebAuthn L3 §5.11)#219AlfioEmanueleFresta wants to merge 21 commits into
Conversation
00b7b74 to
b98aa45
Compare
…nt arg Adds the http parameter to FromIdlModel::from_idl_model and the default WebAuthnIDL::from_json; updates the make_credential and get_assertion impls to take it (currently unused, wired in next commit). Updates ceremony examples and existing from_json tests to pass &NoRelatedOriginsClient and .await the calls.
…d get_assertion When the rp.id is not a registrable suffix of the caller's effective domain, call validate_related_origins() per WebAuthn L3 §5.11.1. On success, accept the request; on failure, surface the existing MismatchingRelyingPartyId variant unchanged so callers' pattern matches keep working.
…l and get_assertion
reqwest's referer() defaults to true, so on any redirect chain it would
auto-populate Referer with the previous URL, leaking the RP's well-known
URL to the redirect target. The previous empty-valued Referer default
header did not disable this. Drop the header insertion and call
.referer(false) so no Referer is sent on the initial request or any
redirect, matching WebAuthn L3 \xc2\xa75.11.1 step 2 ("without a referrer").
Extend the trait doc to bind impls to status-200-only and unmodified Content-Type reporting, so a third-party client cannot accidentally feed a 404 body or a synthesised application/json type to the validator.
The cap is hard-coded inside the validator loop, so external callers cannot override it. Reduce to a private const and drop the re-export to avoid committing to the value across breaking changes.
The previous warn!(error = ?err, ...) debug-printed RelatedOriginsError, which can carry reqwest error text (IP/port) and serde_json text (body snippets). Add RelatedOriginsError::kind() that returns a static discriminant and log only that. Downgrade to debug! since most RPs do not host /.well-known/webauthn and the failure is expected noise.
The previous impl re-parsed the listed URL through Origin::parse, which rejects userinfo, non-/ paths, queries and fragments. WebAuthn L3 \xc2\xa75.11.1 step 4.f defers to HTML \xc2\xa77.5 same-origin, which compares only scheme, host and port. Compare those three directly so a listed entry like "https://example.com/foo" can match the caller. Add a test.
Symmetric to label_cap_blocks_sixth_distinct_label_match. Asserts that the 5th distinct label still satisfies step 4.e's size < max check, so an off-by-one regression on the cap is caught by tests.
The body asserts that an IPv6 listed entry is silently skipped at step 4.c/4.d (no registrable label), not that same-origin matches. Rename to match and rephrase the comment.
…d_origins Mirror NoRelatedOriginsClient's placement: under the same feature gate, expose ReqwestRelatedOriginsClient (and HttpPolicy) at the related_origins module root so consumers do not need the http:: submodule path.
…iginsClient Using NoRelatedOriginsClient in the bundled examples taught readers the wrong default. Wire up the reqwest-backed convenience client instead, gate the three webauthn ceremony examples on the related-origins-client feature, and update the README run commands. Also re-exports HttpPolicy and ReqwestRelatedOriginsClient at ops::webauthn so examples import from a single path.
…egration test Swap brand.com/app.brand.org for example.org/app.example.com. RFC 2606 reserves example.* for documentation, so it cannot accidentally collide with a real party. The two-eTLD shape that exercises the related-origins fetch path is preserved.
…nFetchError The trait's old return type was the full RelatedOriginsError, but four of its five variants (UnexpectedContentType, MalformedJson, MalformedDocument, NoMatchingOrigin) are produced inside validate_related_origins after the fetch returns. Implementers had no reason to ever emit them. Introduce WellKnownFetchError with the variants a fetcher can actually emit (Transport, Status, BodyTooLarge, NotSupported) and let RelatedOriginsError wrap it via a Fetch variant with #[from]. The reqwest client now distinguishes non-200 status from transport faults and from body-cap hits without stringifying everything. Also drops RelatedOriginsError::kind(); the two debug! call sites switch to logging the Display form of the error directly.
05f055e to
2a4d4e5
Compare
msirringhaus
left a comment
There was a problem hiding this comment.
Looking good overall. One architectural question, but I'm fine with the way it is currently, too.
| pub body: Vec<u8>, | ||
| } | ||
|
|
||
| /// Fetcher for `https://{rp_id}/.well-known/webauthn`, per WebAuthn L3 §5.11.1 |
There was a problem hiding this comment.
Not sure how relevant this is, as we don't expect to get a lot of implementations for this, other than reqwest, but:
I wonder if it would be 'safer' to have the client JUST do the http-request and nothing else.
Meaning: validate_related_origins() will already create the well-known-url, fetch_well_known() (which would then just be fetch()) would simply fetch the website (not even knowing what URL that is), return status code, transport-type and optionally content back to validate_related_origins(), which does all the checks.
Doing that would make it way harder to mess up an implementation (e.g. forgetting to implement some checks that are only documented in the comment here). The only contract would then be to follow redirects and cap body size.
Implements WebAuthn L3 §5.11 "Related Origins": when a request's rp.id is not a registrable suffix of the caller's effective domain, libwebauthn now fetches the RP's
.well-known/webauthndocument and accepts the request if a listed origin matches the caller.Closes #160. Stacked on #215; depends on its public-suffix-list trait. Based on #173 by @HarveyOrourke15.
The HTTP fetcher is pluggable via a trait. A reqwest-backed default ships behind the optional
related-origins-clientcargo feature so the core crate stays HTTP-client-free.Note: the JSON-request parsing trait becomes async and gains an HTTP client parameter; downstream consumers will need a one-line update at call sites.